import 'package:my_flutter_stomp_core/services/stomp_subscriber.dart';
import 'package:stomp_dart_client/stomp_dart_client.dart';

class StompClientService {
  static const String stateDisconnected = 'disconnected';
  static const String stateDisconnecting = 'disconnecting';
  static const String stateConnected = 'connected';

  static String _currentState = stateDisconnected;

  static StompClient? _stompClient;

  // Using a map for storing clients instead of a list provides several advantages:
  //
  // Efficient Lookup: Maps provide constant-time lookup complexity, O(1), which means finding a client by its
  // connection object is very fast, regardless of the number of clients connected. In contrast, finding a client
  // in a list would require iterating through the list, resulting in linear-time complexity, O(n), where n is the number of clients.
  //
  // Subscription Management: When clients subscribe to different destinations, using a map allows you to organize
  // subscriptions efficiently. Each destination can map to a set of clients, enabling quick access to clients subscribed
  // to a specific destination. This organization facilitates targeted message delivery based on subscription topics.
  //
  // Easy Removal: Removing a client from a map is straightforward and efficient. With maps, you can directly delete a client
  // entry by its connection object, whereas removing a client from a list involves searching the list for the client and then removing it,
  // resulting in potentially higher time complexity.
  //
  // Avoiding Duplicate Entries: Maps inherently enforce uniqueness in keys. This property ensures that each client connection is
  // uniquely identified, preventing duplicate entries. In contrast, a list allows duplicate entries, which might complicate management,
  // especially if you want to ensure that each client is only subscribed once to a particular destination.
  //
  // While lists can be suitable for maintaining a collection of clients in some scenarios, using a map offers better performance and
  // organization, particularly in scenarios involving subscription management and efficient client lookup, which are common requirements
  // in WebSocket applications. Therefore, using a map is a more optimal choice for storing WebSocket clients, especially when considering
  // scalability and performance aspects.
  static final Map<StompSubscriber, bool> _subscribers = {};

  static String get currentState {
    return _currentState;
  }

  /// Initializes a web socket connection and its callbacks.
  /// With [endpoint] ws/wss for stomp. http/https for socketjs.
  static void init({
    required String endpoint,
    Map<String, String>? connectHeaders,
    void Function()? onConnect,
    void Function()? onDisconnect,
    void Function(String state)? onChangeState,
    void Function(dynamic err)? onWebSocketError,
    void Function(dynamic err)? onStompError,
  }) {
    // Creates Map<Symbol, dynamic> for Function.apply usage.
    final Map<Symbol, dynamic> configParam = {
      const Symbol('url'): endpoint,
      const Symbol('stompConnectHeaders'): connectHeaders,
      const Symbol('onConnect'): (frame) {
        _currentState = stateConnected;
        onConnect?.call();
        onChangeState?.call(_currentState);
      },
      const Symbol('onDisconnect'): (frame) {
        _currentState = stateDisconnected;
        onDisconnect?.call();
        onChangeState?.call(_currentState);
      },
      const Symbol('onWebSocketError'): (err) => onWebSocketError?.call(err),
      const Symbol('onStompError'): (err) => onStompError?.call(err),
    };

    // You will find out 'new' is NOT pop up with IntelliSense but it does exist.
    final configFn = endpoint.toLowerCase().startsWith('ws')
        ? StompConfig.new
        : StompConfig.sockJS;

    // With Function.apply we can use map parameter to create a class instance.
    _stompClient =
        StompClient(config: Function.apply(configFn, null, configParam));
  }

  /// Opens web socket connection.
  static void connect() {
    _stompClient!.activate();
  }

  /// Clears all stomp clients and their subscriptions and closes web socket connection.
  static void disconnect() {
    clear();
    _stompClient!.deactivate();
  }

  /// Spawns a StompSubscriber over current web socket connection.
  /// Also stores StompSubscriber in local list for auto-clear usage.
  static StompSubscriber spawn() {
    final stompSubscriber = StompSubscriber(_stompClient!);
    _subscribers[stompSubscriber] = true;

    return stompSubscriber;
  }

  /// Clears a StompSubscriber and removes it from local list.
  static void remove(StompSubscriber stompSubscriber) {
    stompSubscriber.clear(null);
    _subscribers.remove(stompSubscriber);
  }

  /// Clears all StompSubscribers and removes them from local list.
  static void clear() {
    while (_subscribers.keys.isNotEmpty) {
      remove(_subscribers.keys.first);
    }

    // for (final key in _subscribers.keys) {
    //   remove(key);
    // }
  }
}
